﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using MPI;


namespace SVM {
    //xww: modified to enable parallel function.
    public static partial class ParameterSelection {
        ///<summary>Determin whether to use parallel function</summary>        
        public static ParallelMode ParallelModeSetting { get; set; }
        public static bool UseMPI { get; set; }
        /// <summary>
        /// Parallel computing mode when selecting parameters.
        /// </summary>
        public enum ParallelMode {
            /// <summary>Signle CPU on host</summary>
            None=0, 
            ///<summary>Multi-CPU on host</summary>
            NoramlParallel,
            ///<summary>Multi-CPU on multi hosts, MPI-2 standard implemented.</summary>
            MPI
        }

        /// <summary>
        /// Performs a Grid parameter selection, trying all possible combinations of the two lists and returning the
        /// combination which performed best.  Use this method if validation data isn't available, as it will
        /// divide the training data and train on a portion of it and test on the rest.
        /// </summary>
        /// <param name="problem">The training data</param>
        /// <param name="parameters">The parameters to use when optimizing</param>
        /// <param name="CValues">The set of C values to use</param>
        /// <param name="GammaValues">The set of Gamma values to use</param>
        /// <param name="outputFile">Output file for the parameter results.</param>
        /// <param name="nrfold">The number of times the data should be divided for validation</param>
        /// <param name="C">The optimal C value will be placed in this variable</param>
        /// <param name="Gamma">The optimal Gamma value will be placed in this variable</param>
        public static void GridParallel(
            Problem problem,
            Parameter parameters,
            List<double> CValues,
            List<double> GammaValues,
            string outputFile,
            int nrfold,
            out double C,
            out double Gamma) {
            C = 0;
            Gamma = 0;
            _crossValidation = double.MinValue;
            StreamWriter output = new StreamWriter(outputFile);

            Parameter[] gridParams = new Parameter[CValues.Count * GammaValues.Count];
            for (int i = 0; i < CValues.Count; i++) {
                for (int j = 0; j < GammaValues.Count; j++) {
                    int index = i * GammaValues.Count + j;
                    gridParams[index] = parameters.Clone() as Parameter;
                    gridParams[index].C = CValues[i];
                    gridParams[index].Gamma = GammaValues[j];
                }
            }
            object locker = new object();
            _tempC = _tempGamma = double.MinValue;
            //edit
#if NORMAL_PARALLEL
            gridParams.AsParallel().ForAll(o => {
                double test = Training.PerformCrossValidation(problem, o, nrfold);
                lock (locker) {
                    Console.Write("{0} {1} {2}", o.C, o.Gamma, test);
                    output.WriteLine("{0} {1} {2}", o.C, o.Gamma, test);
                    if (test > crossValidation) {
                        tempC = o.C;
                        tempGamma = o.Gamma;
                        crossValidation = test;
                        Console.Write(" New Maximum!");output.Write(" New Maximum!");
                    }
                    Console.WriteLine(); output.WriteLine();
                }
            });
#else
            MPIServerClientTask TASK = new MPIServerClientTask();
            Queue<Parameter> paramQueue = new Queue<Parameter>(gridParams);
            ReceiveRequestList requestsList = new ReceiveRequestList();
            for (int i = 0; i < TASK.ClientIDs.Length && i < paramQueue.Count; i++) {
                var taskunit = new ParameterSelectionTaskUnitIn { Problem = problem, Param = paramQueue.Dequeue(), NFold = nrfold };
                TASK.Comm.Send<ParameterSelectionTaskUnitIn>(taskunit, TASK.ClientIDs[i], 0);
                requestsList.Add(TASK.Comm.ImmediateReceive<ParameterSelectionTaskUnitOut>(TASK.ClientIDs[i], 0));
                TASK.ReceiveOneTaskFromServer(TASK.ClientIDs[i]);
            }
            while (paramQueue.Count > 0) {
                var completedReturns = requestsList.WaitSome().Select(o => o.GetValue() as ParameterSelectionTaskUnitOut).ToList();
                foreach (var taskOut in completedReturns) {
                    TASK.SendOneTaskToServer(taskOut.ClientId);
                    OnNewParamSelected(output, taskOut);
                    if (paramQueue.Count <= 0)
                        continue;
                    TASK.Comm.Send<ParameterSelectionTaskUnitIn>(new ParameterSelectionTaskUnitIn { Problem = null, Param = paramQueue.Dequeue(), NFold = nrfold }, taskOut.ClientId, 0);
                    requestsList.Add(TASK.Comm.ImmediateReceive<ParameterSelectionTaskUnitOut>(taskOut.ClientId, 0));
                    TASK.ReceiveOneTaskFromServer(taskOut.ClientId);
                }
            }

            while (requestsList.Count > 0) {
                var finalFequests = requestsList.WaitSome().Select(o => o.GetValue() as ParameterSelectionTaskUnitOut).ToList();
                foreach (var taskOut in finalFequests) 
                    OnNewParamSelected(output, taskOut);
            }


#endif
            C = _tempC; Gamma = _tempGamma;
            output.Close();
        }


        public static void GridParallel(Problem problem, Problem validation, Parameter parameters,
            List<double> CValues, List<double> GammaValues, string outputFile,
            out double C, out double Gamma) 
        {
            C = 0;
            Gamma = 0;
            double maxScore = double.MinValue;
            StreamWriter output = new StreamWriter(outputFile);

            Parameter[] gridParams = new Parameter[CValues.Count * GammaValues.Count];
            for (int i = 0; i < CValues.Count; i++)
                for (int j = 0; j < GammaValues.Count; j++) {
                    int index = i * GammaValues.Count + j;
                    gridParams[index] = parameters.Clone() as Parameter;
                    gridParams[index].C = CValues[i];
                    gridParams[index].Gamma = GammaValues[j];
                }
            object locker = new object();
            double tempC, tempGamma;
            tempC = tempGamma = double.MinValue;
            //edit
            gridParams.AsParallel().ForAll(o => {
                Model model = Training.Train(problem, o);
                string fileName = string.Format("tmp_C_{0}_Gamma_{1}.txt", o.C, o.Gamma);
                double test = Prediction.Predict(validation, fileName, model, false);
                lock (locker) {
                    Console.Write("{0} {1} {2}", o.C, o.Gamma, test);
                    output.WriteLine("{0} {1} {2}", o.C, o.Gamma, test);
                    if (test > maxScore) {
                        tempC = o.C;
                        tempGamma = o.Gamma;
                        maxScore = test;
                        Console.Write(" New Maximum!"); output.Write(" New Maximum!");
                    }
                    Console.WriteLine(); output.WriteLine();
                }
            });
            C = tempC; Gamma = tempGamma;
            output.Close();
        }

        static double _tempC, _tempGamma, _crossValidation;

        private static void OnNewParamSelected(StreamWriter output, ParameterSelectionTaskUnitOut taskOut) {
            Console.Write("[{3}]\t[{4}]\t{0} {1} {2}", taskOut.C, taskOut.Gamma, taskOut.CrossValidationScore, taskOut.ClientId, DateTime.Now);
            output.Write("[{3}]\t[{4}]\t{0} {1} {2}", taskOut.C, taskOut.Gamma, taskOut.CrossValidationScore, taskOut.ClientId, DateTime.Now);
            if (taskOut.CrossValidationScore > _crossValidation) {
                _tempC = taskOut.C;
                _tempGamma = taskOut.Gamma;
                _crossValidation = taskOut.CrossValidationScore;
                Console.Write(" New Maximum!"); output.Write(" New Maximum!");
            }
            Console.WriteLine(); output.WriteLine();
        }
    
    }
    [Serializable]
    public class ParameterSelectionTaskUnitIn {
        public Problem Problem {get;set;}
        public Parameter Param { get; set; }
        public int NFold { get; set; }
    }
    [Serializable]
    public class ParameterSelectionTaskUnitOut {
        public double CrossValidationScore { get; set; }
        public double C { get; set; }
        public double Gamma { get; set; }
        public int ClientId { get; set; }
    }

}
